package com.gopay.checkorder.service.task;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gopay.checkorder.dao.BisBankOrderQueryDAO;
import com.gopay.checkorder.service.BankOrderService;
import com.gopay.checkorder.service.alarm.OrderAlarmDataCollector;
import com.gopay.checkorder.service.alarm.OrderAlarmRecordStorage;
import com.gopay.checkorder.service.task.thread.CheckOrderThread;
import com.gopay.common.constants.bank.BankOrderStatus;
import com.gopay.common.constants.bank.BankOrderType;
import com.gopay.common.constants.bank.BisBankBusinessProd;
import com.gopay.common.domain.bank.BankOrder;
import com.gopay.common.util.DateUtils;

/**
 * 退款交易查询定时任务
 * 
 * @corpRefundCheckOrderJob.java
 * @author zhengxue
 * @2016-9-5 上午10:19:46 www.gopay.com.cn Inc.All rights reserved.
 */
public class corpRefundCheckOrderJob extends Observable implements Task {
    private final static Logger logger = LoggerFactory.getLogger(corpRefundCheckOrderJob.class);

    private final static int MAX_SIZE = 10000;

    @Resource(name = "bisBankOrderQueryDAO")
    private BisBankOrderQueryDAO bisBankOrderQueryDAO;

    private final static int THREAD_SIZE = 10;
    private ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);

    @Resource(name = "orderAlarmRecordStorage")
    private OrderAlarmRecordStorage orderAlarmRecordStorage;

    @Resource(name = "bankOrderService")
    private BankOrderService bankOrderService;

    private void printLog(StopWatch sw, String prefix) {
        sw.split();
        logger.error(prefix + "------,耗时=" + sw.getSplitTime());
    }

    @Override
    public void doTask() {
        try {
            StopWatch sw = new StopWatch();
            sw.start();
            printLog(sw, "corpRefundCheckOrderJob------start");

            int pageSize = 100;
            int count = 0;

            Date date = new Date();
            int resCount = getCheckOrderCount(date);
            printLog(sw, "corpRefundCheckOrderJob------对单总数量=" + resCount);
            // 订单对账报警采集者
            OrderAlarmDataCollector alarmCollector = new OrderAlarmDataCollector();
            List<BankOrder> list = null;
            while (count < resCount) {
                list = getCheckOrder(date, 1, pageSize);
                if (list == null || list.size() == 0) {
                    printLog(sw, "corpRefundCheckOrderJob------分页为空，跳出while循环");
                    break;
                }
                printLog(sw, "corpRefundCheckOrderJob------list.size()=" + list.size() + ",count=" + count);

                //
                // 订单队列
                ConcurrentLinkedQueue<BankOrder> queue = new ConcurrentLinkedQueue<BankOrder>();
                queue.addAll(list);

                // 同步锁存器
                CountDownLatch latch = new CountDownLatch(queue.size());

                // 线程池中线程全部参与
                for (int i = 0; i < THREAD_SIZE; i++) {
                    CheckOrderThread thread = new CheckOrderThread(bankOrderService, queue, latch, alarmCollector);
                    threadPool.execute(thread);

                }

                // 同步锁存器等待所有线程执行完成
                if (!latch.await(118, TimeUnit.SECONDS)) {
                    logger.error("同步锁存器等待超时了 latch.count=" + latch.getCount());
                    threadPool.awaitTermination(1, TimeUnit.SECONDS);
                }

                count += pageSize;
            }

            // 判断报警阈值并保存报警记录
            orderAlarmRecordStorage.store(alarmCollector);

            printLog(sw, "corpRefundCheckOrderJob------结束while循环,resCount=" + resCount);

            printLog(sw, "corpRefundCheckOrderJob------end");
            sw.stop();
        } catch (Throwable t) {
            logger.error("corpRefundCheckOrderJob执行对单Task时出现异常", t);
        }
    }

    /**
     * 待对账订单笔数
     * 
     * @param orderStatus
     * @param totalCheckTimes
     * @return
     */
    protected int getCheckOrderCount(Date date) {
        Map<String, Object> params = new HashMap<String, Object>();
        StringBuffer hql = new StringBuffer("select count(*) ");
        hql.append(buildHqlAndParamsOfCorpRefund(params));

        int resCount = bisBankOrderQueryDAO.findCountByHql(hql.toString(), params);
        if (resCount > MAX_SIZE) {
            resCount = MAX_SIZE;
        }
        return resCount;
    }

    /**
     * 分页查询对账记录
     * 
     * @param date
     * @param pageIndex
     * @param pageSize
     * @return
     */
    protected List<BankOrder> getCheckOrder(Date date, int pageIndex, int pageSize) {
        Map<String, Object> params = new HashMap<String, Object>();

        StringBuilder sb = new StringBuilder();
        sb.append(buildHqlAndParamsOfCorpRefund(params));
        sb.append(" order by totalCheckTimes asc ");

        return bisBankOrderQueryDAO.findByHql(sb.toString(), params, pageIndex, pageSize);
    }

    private String buildHqlAndParamsOfCorpRefund(Map<String, Object> params) {
        StringBuffer hql = new StringBuffer("");
        hql.append(" from BankOrder ");
        hql.append(" where orderStatus in (:orderStatus00,:orderStatus92)  ");
        hql.append(" and bankBizProdCode = :bankBizProdCode  ");
        hql.append(" and orderType = :orderType ");
        hql.append(" and sendBankTm >to_date(:beginTime,'yyyyMMddHH24miss') ");

        // map
        params.put("orderStatus00", BankOrderStatus.PROCESS);
        params.put("orderStatus92", BankOrderStatus.VERIFY_FAIL);
        params.put("bankBizProdCode", BisBankBusinessProd._12.value);
        params.put("orderType", BankOrderType.GOPAY_BIS_ORDER);

        // 开始时间--最近7天
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DAY_OF_MONTH, -7);
        params.put("beginTime", DateUtils.format(calendar.getTime(), "yyyyMMddHHmmss"));

        return hql.toString();
    }

}
